#include "Star2.h"
#include <exception>
#include <cfloat>
#include "CommonApps.h"
//C'tor
//Expectimax::Expectimax(SenetHeuristics& heurisitic): m_heuristic(heurisitic){}

void Star2::Init(const SenetPlayerExt* owner , int gameStickNum)
{
	m_gameStickNum = gameStickNum;
	m_probabilities.init(gameStickNum);
	m_heuristic.init(owner , m_probabilities); 
	m_expectimax.Init(owner, gameStickNum);
	
}

// Assignment operator
//Expectimax& Expectimax::operator =(const Expectimax& e)
//{
//	m_heuristic = e.m_heuristic;
//	m_gameStickNum = e.m_gameStickNum;
//	return *this;
//}


//calculate negamax
void Star2::negamax(
						Score& res ,
						SenetBoardDraft& board,
						int depth,
						const GameTimer& timer,
						bool checkTime,
						Definitions::PlayerColor currentPlayer,
						int stickNum /* the number of sticks that current player got */,
						NodeType nodeType,
						float alpha,
						float beta)
{
	if(checkTime && timer.isMoveTimePassed(GameTimer::NO_OVERHEAD))
	{
		throw Exception::TIME_OUT;
	}
	res.pieceToMove = -1;
	if(board.isTerminal() || depth == 0)
	{
		res.score = m_heuristic.score(board, currentPlayer);
	}
	else
	{
		//TODO: Debug:
		alpha = CommonApps::floatMax(alpha, m_heuristic.getLowerHeuristicBound());
		beta = CommonApps::floatMin(beta, m_heuristic.getUpperHeuristicBound());


		// generate moves
		vector<int> moves;
		board.generateMoves(moves, currentPlayer, stickNum);
		int movesSize = moves.size();
		if(movesSize == 0)
		{
			res.score = m_heuristic.score(board, currentPlayer);
			return;
		}

		// for each child
		for(int i = 0 ;	i < movesSize;	++i)
		{
			int startLoc = moves[i];
			int endLoc = startLoc + stickNum;

			//do move
			vector<SenetBoardDraftMove> movesPerformed;
			board.doMove(movesPerformed, startLoc, endLoc, currentPlayer);

			//score move
			Score val = chanceSearch(
				board , depth-1 ,
				timer, checkTime ,
				Definitions::getOppositePlayer(currentPlayer),
				i , CHANCE, CommonApps::getOppositeNodeType(nodeType), -beta, -alpha);
			val.score *= -1.0f;

			//undo move
			CommonApps::undoMoves(movesPerformed, board , currentPlayer);

			// update the current maximum score
			// the following is actually: current_max = max(current_max, val)
			if(val.score > alpha)
			{
				if (val.score >= beta)
				{
					//res.score = val.score;
					res.score = beta;
					res.pieceToMove = startLoc;
					return;
				}
				alpha = val.score;
				res.pieceToMove = startLoc;
			}
		}
		res.score = alpha;
	}	
}

void Star2::negamaxProbe(
						Score& res ,
						SenetBoardDraft& board,
						int depth,
						const GameTimer& timer,
						bool checkTime,
						Definitions::PlayerColor currentPlayer,
						int stickNum /* the number of sticks that current player got */,
						NodeType nodeType,
						float alpha,
						float beta)
{
	if(checkTime && timer.isMoveTimePassed(GameTimer::NO_OVERHEAD))
	{
		throw Exception::TIME_OUT;
	}
	res.pieceToMove = -1;
	if(board.isTerminal() || depth == 0)
	{
		res.score = m_heuristic.score(board, currentPlayer);
	}
	else
	{
		//TODO Debug: ************* attempt to fix see if help and decide if to leave
		alpha = CommonApps::floatMax(alpha, m_heuristic.getLowerHeuristicBound());
		beta = CommonApps::floatMin(beta, m_heuristic.getUpperHeuristicBound());


		// generate moves
		vector<int> moves;
		board.generateMoves(moves, currentPlayer, stickNum);
		int movesSize = moves.size();
		if(movesSize == 0)
		{ 
			res.score = m_heuristic.score(board, currentPlayer);
			return;
		}
		//TODO: Debug: select i more intellegently
		// for each child
		for(int i = 0 ; i < movesSize;	++i)
		{
			int startLoc = moves[i];
			int endLoc = startLoc + stickNum;

			//do move
			vector<SenetBoardDraftMove> movesPerformed;
			board.doMove(movesPerformed, startLoc, endLoc, currentPlayer);

			//score move
			Score val = chanceProbe(
				board , depth-1 ,
				timer, checkTime ,
				Definitions::getOppositePlayer(currentPlayer),
				i , CHANCE, CommonApps::getOppositeNodeType(nodeType), -beta, -alpha);
			val.score *= -1.0f;

			//undo move
			CommonApps::undoMoves(movesPerformed, board , currentPlayer);

			// update the current maximum score
			// the following is actually: current_max = max(current_max, val)
			if(val.score > alpha)
			{
				if (val.score >= beta)
				{
					//res.score = val.score;
					res.score = beta;
					res.pieceToMove = startLoc;
					return;
				}
				alpha = val.score;
				res.pieceToMove = startLoc;
			}
		}
		res.score = alpha;
	}	
}
//calculate value at chance nodes
void Star2::expectimax(Score& res, SenetBoardDraft &board, int depth, const GameTimer& timer, bool checkTime , 
						Definitions::PlayerColor nextPlayer, NodeType nextTurn, float alpha, float beta) throw (Exception)
{
	if(checkTime && timer.isMoveTimePassed(GameTimer::NO_OVERHEAD))
	{
		throw Exception::TIME_OUT;
	}
	if(board.isTerminal() || depth == 0)
	{
		res.pieceToMove = -1;
		res.score = m_heuristic.score(board, nextPlayer);
	}
	else
	{
		//TODO Debug: ************* attempt to fix see if help and decide if to leave
		alpha = CommonApps::floatMax(alpha, m_heuristic.getLowerHeuristicBound());
		beta = CommonApps::floatMin(beta, m_heuristic.getUpperHeuristicBound());


		float vsum = 0.0f;
	
		// chance node: no piece is selected here
		res.pieceToMove = -1;

		// get L and U
		float L = m_heuristic.getLowerHeuristicBound();
		float U = m_heuristic.getUpperHeuristicBound();

		// init Yi,Xi
		float Yi = 1.0f; // this is Y0
		float Xi = 0.0f; // this is X1
		
		vector<float> wiArray;
		wiArray.resize(m_gameStickNum + 1);
		//float* wiArray = new float[m_gameStickNum + 1];

		// **************** Star2 probing phase **************************
		for (int i = 0; i <= m_gameStickNum; ++i)
		{
			Yi = Yi - m_probabilities.getProbability(i);	// this is Yi+1

			float Bi = ((beta - L*Yi - Xi) / m_probabilities.getProbability(i));
			float Bx = CommonApps::floatMin(Bi, U);

			Score val = chanceProbe(board, depth-1,timer, checkTime, nextPlayer, i, nextTurn, NodeType::DONT_CARE, L, Bx);				
 			wiArray[i] = val.score; // wiArray[i] is not normalized.
			 
			//TODO: debug
			/*m_expectimax.scoreExpectimax(board, depth-1, timer, checkTime, nextPlayer, i);
			float expVal = m_expectimax.FinalBoardScore();
			if(expVal < wiArray[i] && nextTurn == NodeType::MAX)
			{
				int x;
				x=1;
			}*/

			//TODO: Debug
			//wiArray[i] -= 5;

			vsum += (wiArray[i] * m_probabilities.getProbability(i));
			
			if (wiArray[i] >= Bi)
			{
				vsum += (L * getProbabilityLeft(i)); //Original
				res.score = vsum;
				return;
			}

			

			// continue to next iteration
			Xi = Xi + m_probabilities.getProbability(i) * val.score;	// this is Xi+1
		}

		//************************* search phase *****************************

		// re-init Yi,Xi,vsum
		Yi = 1.0f; // this is Y0
		Xi = 0.0f; // this is X1
		vsum = 0.0f;

		// normalize wiArrayVector
		for (int i = 0; i <= m_gameStickNum; ++i)
		{
			//TODO Debug: return this
			wiArray[i] *= m_probabilities.getProbability(i);
		}

		float Wi = getSumWi(wiArray); // this is W0

		// Star1 search phase
		// for-each chance successor
		for(int i = 0; i <= m_gameStickNum; ++i)
		{
			Yi = Yi - m_probabilities.getProbability(i);	// this is Yi+1
			Wi -= wiArray[i]; // this is Wi+1

			float Ai = ((alpha - U*Yi - Xi) / m_probabilities.getProbability(i));
			float Bi = (beta - Wi - Xi) / m_probabilities.getProbability(i);

			//TODO: Debug:
			///*float BiStar1 = ((beta - L*Yi - Xi) / m_probabilities.getProbability(i));
			//if(BiStar1 < Bi)
			//{
			//	int x;
			//	x = 100;
			//}
			//Bi = BiStar1;*/


			float Ax = CommonApps::floatMax(Ai, L);
			float Bx = CommonApps::floatMin(Bi, U);

			Score val = chanceSearch(
				board, depth-1, timer,
				checkTime, nextPlayer, i,
				nextTurn, DONT_CARE, Ax, Bx);
			vsum += (val.score * m_probabilities.getProbability(i));

			// check fail-soft
 			if (val.score <= Ai)// && nextTurn == NodeType::MAX)
			{
				vsum += (U * Yi); //Original
				//vsum += (U * getProbabilityLeft(i)); //Original
				//vsum += (L * getProbabilityLeft(i));
				res.score = vsum;
				return;
			}
			if (val.score >= Bi)// && nextTurn == NodeType::MIN)
			{
				vsum += Wi;
				//vsum += (L * getProbabilityLeft(i)); //Original				
				//vsum += (U * getProbabilityLeft(i)); //Correction
				res.score = vsum;
				return;
			}

			// continue to next iteration
			//TODO:  this equals to vsum. consider replacing
			Xi = Xi + m_probabilities.getProbability(i) * val.score;	// this is Xi+1
		}
		res.score = vsum;
	}
}


//driver function
Score Star2::chanceSearch(SenetBoardDraft& board, int depth, const GameTimer& timer, bool checkTime ,
						Definitions::PlayerColor currentPlayer, int stickNum ,  NodeType nodeType , NodeType nextTurn,
						float alpha, float beta) throw(Exception)
{	
	Score res;
	if(nodeType == CHANCE)
	{
		expectimax(res , board, depth, timer, checkTime, currentPlayer, nextTurn, alpha, beta);
	}
	else
	{
		//minimax(res , board, depth, timer, checkTime, currentPlayer, stickNum , nodeType);
		negamax(res , board, depth, timer, checkTime, currentPlayer, stickNum , nodeType, alpha, beta);
	}
	return res;
	
}

void Star2::probeChanceNode(Score& res, SenetBoardDraft &board, int depth, const GameTimer& timer, bool checkTime , 
						Definitions::PlayerColor nextPlayer, NodeType nextTurn, float alpha, float beta) throw (Exception)
{
	if(checkTime && timer.isMoveTimePassed(GameTimer::NO_OVERHEAD))
	{
		throw Exception::TIME_OUT;
	}
	if(board.isTerminal() || depth == 0)
	{
		res.score = m_heuristic.score(board, nextPlayer);
	}
	else
	{
		//TODO Debug: ************* attempt to fix see if help and decide if to leave
		/*alpha = CommonApps::floatMax(alpha, m_heuristic.getLowerHeuristicBound());
		beta = CommonApps::floatMin(beta, m_heuristic.getUpperHeuristicBound());*/

		float vsum = 0.0f;
	
		// get L and U
		float L = m_heuristic.getLowerHeuristicBound();
		float U = m_heuristic.getUpperHeuristicBound();

		float Yi = 1.0f; // this is Y0 - the remaining probability which we didn't check yet
		float Xi = 0.0f; // this is X1 - the (value * probability) we already checked

		//TODO: Debug - decide if this is good
		for(int i = 0; i <= m_gameStickNum; ++i)
		{
			Yi = Yi - m_probabilities.getProbability(i);	// this is Yi+1

			float Ai = ((alpha - U*Yi - Xi) / m_probabilities.getProbability(i));
			float Bi = ((beta - L*Yi - Xi) / m_probabilities.getProbability(i));
			float Ax = CommonApps::floatMax(Ai, L);
			float Bx = CommonApps::floatMin(Bi, U);

			Score val = chanceProbe(board, depth-1, timer, checkTime, nextPlayer, i, nextTurn, DONT_CARE, Ax, Bx);
			vsum += (val.score * m_probabilities.getProbability(i));

			// check fail-soft
			if (val.score <= Ai)
			{
				vsum += (U * getProbabilityLeft(i));
				res.score = vsum;
				return;
			}
			if (val.score >= Bi)
			{
				vsum += (L * getProbabilityLeft(i));
				res.score = vsum;
				return;
			}

			// continue to next iteration
			Xi = Xi + m_probabilities.getProbability(i) * val.score;	// this is Xi+1

		}

		res.score = vsum + Yi * U;
	}	

		//TODO: calculate this in advance
		//
		
		//float nodeVal = 0.0;
		////float mediumRange = (float)(m_gameStickNum /2.0f);
		//float sumProbability = 0.0;
		//for(int i=0 ; i <= m_gameStickNum ; ++i)
		//{
		//	float probingAnswer = chanceProbe(board, depth-1, timer, checkTime, nextPlayer, i, nextTurn, DONT_CARE, alpha, beta).score;
		//	nodeVal += (probingAnswer * m_probabilities.getProbability(i));//getProbabilityStick(i , m_gameStickNum));
		//	sumProbability += m_probabilities.getProbability(i);
		//}
		//if(sumProbability !=1)
		//{
		//	throw "ERRORRRRRRR!!!!!!!!!!!!!!";
		//}
		//res.score = nodeVal;// + ((1 - sumProbability)*U);
		//
		/*int i = (m_gameStickNum / 2);
		float probingAnswer = chanceProbe(board, depth-1, timer, checkTime, nextPlayer, i, nextTurn, DONT_CARE, alpha, beta).score;
		res.score = (getProbabilityStick(i,m_gameStickNum) * probingAnswer) + ((1 - getProbabilityStick(i,m_gameStickNum)) * (U));*/
		
		//if(nextTurn == NodeType::MAX)
		//{
		//	//my calling node is MIN - I need to make sure I give him a value I'll eventually be smaller then.
		//	res.score = (getProbabilityStick(i,m_gameStickNum) * probingAnswer) + ((1 - getProbabilityStick(i,m_gameStickNum)) * U);
		//}
		//else
		//{
		//	//my calling node is MAX - I need to make sure I give him a value I'll eventually be larger then.
		//	res.score = (getProbabilityStick(i,m_gameStickNum) * probingAnswer) + ((1 - getProbabilityStick(i,m_gameStickNum)) * L);
		//}
		
	//}	
}

Score Star2::chanceProbe(SenetBoardDraft &board, int depth, const GameTimer& timer, bool checkTime , 
						Definitions::PlayerColor currentPlayer, int stickNum,
						NodeType nodeType, NodeType nextTurn, float alpha, float beta) throw (Exception)
{
	Score res;
	if(nodeType == CHANCE)
	{
		probeChanceNode(res , board, depth, timer, checkTime, currentPlayer, nextTurn, alpha, beta);
	}
	else
	{
		//minimax(res , board, depth, timer, checkTime, currentPlayer, stickNum , nodeType);
		negamaxProbe(res , board, depth, timer, checkTime, currentPlayer, stickNum , nodeType, alpha, beta);
	}
	return res;

}

//main function - init turn parameters and calculation
SenetMove Star2::scoreExpectimax(SenetBoardDraft& board, int depth, const GameTimer& timer , 
									  bool checkTime , Definitions::PlayerColor myColor, int stickNum) throw(Exception)
{	
	//calculate score
	Score  res =  chanceSearch(board, depth, timer, checkTime , myColor, stickNum, MAX , DONT_CARE, -FLT_MAX, FLT_MAX);

	m_FinalBoardScore = res.score;

	SenetMove resMove(res.pieceToMove);	
	return resMove;

}